/*
 * Copyright (c) 2021. $date$
 */

package com.example.firstapplication.hotfix;

import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashSet;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * Create by pengQun
 * Desc:热修复的核心工具类
 */
public class FixDexUtil {

    private static final String TAG = FixDexUtil.class.getSimpleName();
    private static final String DEX_SUFFIX = ".dex";
    private static final String APK_SUFFIX = ".apk";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    public static final String DEX_DIR = "odex";
    private static final String OPTIMIZE_DEX_DIR = "optimize_dex";
    private static HashSet<File> loadedDex = new HashSet<>();

    static {
        loadedDex.clear();
    }

    public static void loadFixedDex(Context context) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
        loadFixedDex(context, null);
    }

    /**
     * 加载补丁
     *
     * @param context      上下文
     * @param patchFileDir 补丁所在的目录
     */
    public static void loadFixedDex(Context context, File patchFileDir) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
        //合并dex（补丁dex合并现有的dex）
        doInjectDex(context, loadedDex);
    }

    /**
     * 验证是否需要热修复
     *
     * @param context context
     */
    public static boolean isGoingToFix(@NonNull Context context) {
        boolean canFix = false;
        File externalFilesDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
        String downPath = externalFilesDir.getAbsolutePath();
        String path007 = downPath + File.separator + "007";
        Log.d(TAG, "path007: " + path007);
//        String pathDex = downPath + File.separator + DEX_DIR;

        File fileDir = new File(path007);
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }

        File[] listFiles = fileDir.listFiles();
        for (File listFile : listFiles) {
            String fileName = listFile.getName();
            if (fileName.startsWith("classes")
                    && (fileName.endsWith(DEX_SUFFIX) ||
                    fileName.endsWith(JAR_SUFFIX) ||
                    fileName.endsWith(ZIP_SUFFIX) ||
                    fileName.endsWith(APK_SUFFIX))) {
                loadedDex.add(listFile);
                canFix = true;
            }
        }
        return canFix;
    }

    private static void doInjectDex(Context context, HashSet<File> hashSet) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
        String optimizeDir = context.getFilesDir().getAbsolutePath()
                + File.separator + OPTIMIZE_DEX_DIR;
        Log.d(TAG, "------> optimizeDir: " + optimizeDir);
        //存放dex的解压目录
        File file = new File(optimizeDir);
        if (!file.exists()) {
            file.mkdirs();
        }

        //1.加载应用程序dex的classLoader
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

        //遍历修复的dex文件
        for (File dexFile : hashSet) {

            //2.加载指定修复dex的classLoader（补丁的classLoader）
            DexClassLoader dexClassLoader = new DexClassLoader(
                    dexFile.getAbsolutePath(),//修复补丁所在的目录
                    file.getAbsolutePath(),//补丁的解压目录（用于jar，zip，zpk格式的补丁）
                    null,//加载dex时需要的库
                    pathClassLoader//父类加载器
            );

            //3.获取dex，开始合并 合并的目标是Element[]
            // BaseDexClassLoader中有变量：DexPathList pathList
            // DexPathList中有变量：Element[] dexElements

            //3.1获取pathList

            //获取补丁中的pathList
            Object dexPathList = getPathList(dexClassLoader);
            //获取当前apk的dex中的pathList
            Object apkPathList = getPathList(pathClassLoader);

            //3.2反射获取element数组

            //获取补丁中的element数组
            Object dexElements = getDexElements(dexPathList);
            //获取apk的dex中的element数组
            Object apkDexElements = getDexElements(apkPathList);

            //4.合并Element数组
            Object combineArray = combineArray(dexElements, apkDexElements);

            //5.重新给PathList里边的Element[] dexElements 赋值

            //一定要重新获取，不要用上面的apkPathList，会报错
            Object pathList = getPathList(pathClassLoader);
            setField(pathList, pathList.getClass(), "dexElements", combineArray);
        }
        Toast.makeText(context, "修复完成", Toast.LENGTH_SHORT).show();

    }

    /**
     * 反射给对象中的属性赋值
     */
    private static void setField(Object obj, Class<?> aClass, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = aClass.getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        declaredField.set(obj, value);
    }

    /**
     * 反射得到对象中的属性值
     *
     * @param obj
     * @param aClass
     * @param fieldName
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getField(Object obj, Class<?> aClass, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Field field = aClass.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(obj);
    }

    /**
     * 反射得到类加载器中的pathList对象
     *
     * @param baseDexClassLoader
     * @return
     */
    private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class<?> aClass = Class.forName("dalvik.system.BaseDexClassLoader");
        return getField(baseDexClassLoader, aClass, "pathList");
    }

    /**
     * 反射得到pathList中的dexElements
     */
    private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException {
        return getField(pathList, pathList.getClass(), "dexElements");
    }

    /**
     * 通过反射创建一个数组，并且合并数组
     */
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class<?> componentType = arrayLhs.getClass().getComponentType();
        //得到左边数组的长度(补丁数组)
        int i = Array.getLength(arrayLhs);
        //得到原数组dex的长度
        int j = Array.getLength(arrayRhs);
        //数组的总长度
        int length = i + j;
        //创建一个类型为class的数组
        Object instance = Array.newInstance(componentType, length);
        //复制数组到目标数组
        System.arraycopy(arrayLhs, 0, instance, 0, i);
        System.arraycopy(arrayRhs, 0, instance, i, j);
        return instance;
    }
}